/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.plc4x.codegen.ast;

import java.util.Collections;
import java.util.List;

/**
 * General Factory method to use for Code Generation.
 * Generally, the Structure which is generated by this class is a (incomplete) Abstact Syntax Tree
 * which covers a subset of things which is possible in many programming languages.
 * This tree is the basis for the generation of code in many languages which is performed by
 * the implementations of the {@link Generator} interface.
 *
 * In this sense the AST is Language Agnostic and should be seen as such. It is oriented at Java
 * as the whole thing is java based but is a bit more generic that it would need to be for java alone.
 * This is something to keep in mind.
 *
 * So some examples on how to implement some things with the AST.
 *
 * Variable Definition / Assignment
 * <code>
 *   Expressions.declaration("a", Primitive.DOUBLE)
 * </code>
 * is similar to
 * <code>
 *   Double a;
 * </code>
 * in java, or in Python
 * <code>
 *   a: float = None
 * </code>
 * (we use Type Hints) and Python cannot declare variables without initialization.
 *
 * Another thing is to initialize a variable with a constant value:
 * <code>
 *   Expressions.declaration("a", Expressions.constant(5.0))
 * </code>
 * this will lead (in Java) to
 * <code>
 *   Double a = 5.0;
 * </code>
 * The type is inferred for the constant expression from 5.0 and is then passed to the variable.
 * In Python this will become
 * <code>
 *   a: float = 5.0
 * </code>
 *
 * If Statement:
 * A more complex example with an if statement is
 * <code>
 *   Block.build()
 *      .add(Expressions.declaration("pi", Primitive.DOUBLE))
 *      .add(
 *          Expressions.ifElse(
 *             Expressions.binaryExpression(
 *                 Expressions.BOOLEAN,
 *                 Expressions.parameter("x", Primitive.DOUBLE),
 *                 Expressions.constant(5.0),
 *                 BinaryExpression.Operation.EQ
 *             ),
 *             Block.build().add(
 *                 Expressions.assignment(
 *                     Expressions.parameter("pi"),
 *                     Expressions.constant(3.141)
 *                 )
 *            )
 *      )
 *      .toBlock()
 * </code>
 * This could will transform to something like (in Java)
 * <code>
 *   Double pi;
 *   if (x == 5.0) {
 *     pi = 3.141;
 *   }
 * </code>
 *
 * More Examples of (complex) structures can be found, e.g., in the
 * {@link org.apache.plc4x.codegen.util.PojoFactory} or in the {@link org.apache.plc4x.codegen.util.EnumFactory}
 * which are "layers" on top of this AST whcih make it easier to create POJOs or ENUM-like constructs.
 * They define the class and all fields / methods / getters / setters and constructor and such stuff.
 */
public class Expressions {

    private Expressions() {
        // do not instantiate
    }

    /**
     * Assign a value to a target (field or parameter)
     * @param target Where the value is assigned
     * @param value What to assign
     * @return Assignment Expression
     */
    public static Expression assignment(Expression target, Node value) {
        return new AssignementExpression(target, value);
    }

    /**
     * Base for all Binary Expression, i.e., Expressions which take
     * two inputs and return one Output.
     * Examples are Comparators, Math, ... .
     * @param type Type of the returned expression
     * @param left
     * @param right
     * @param op
     * @return
     */
    public static Expression binaryExpression(TypeDefinition type, Node left, Node right, BinaryExpression.Operation op) {
        return new BinaryExpression(type, left, right, op);
    }

    /**
     * A Block of code.
     * @param statements
     * @return
     */
    public static Statement block(List<Node> statements) {
        return new Block(statements);
    }

    /**
     * A block of code.
     * @param statements
     * @return
     */
    public static Statement block(Node... statements) {
        return new Block(statements);
    }

    /**
     * Regular (dynamic) call.
     * @param instance
     * @param method
     * @param arguments
     */
    public static Expression call(Node instance, Method method, Node... arguments) {
        return new CallExpression(method, instance, arguments);
    }

    /**
     * Static call (call to a static method)
     * 
     * TODO check if Method is static
     * 
     * @param method
     * @param arguments
     */
    public static Expression staticCall(Method method, Node... arguments) {
        return new CallExpression(method, null, arguments);
    }

    /**
     * Simple if-then-else.
     * If no else is needed, orElse can be null.
     */
    public static Statement ifElse(Expression condition, Block then, Block orElse) {
        return new IfStatement(condition, then, orElse);
    }

    /**
     * Simple if-then-else.
     * If no else is needed, orElse can be null.
     */
    public static Statement ifElse(Expression condition, Block then) {
        return new IfStatement(condition, then, null);
    }

    /**
     * Conditional Statement of the form
     * <code>
     * if (cond1) {
     *  ...
     * } else if (cond2) {
     *  ...
     * } else {
     *  ...
     * }
     * </code>
     */
    public static Statement conditionalStatement(List<Expression> condition, List<Block> blocks) {
        return new IfStatement(condition, blocks);
    }

    /**
     * Defines a compile time constant and infers the type based on what java would do.
     */
    public static Expression constant(Object value) {
        return new ConstantExpression(value);
    }

    /**
     * Define a compile time constant and also passes
     * the expected type for usage in the code generation later.
     */
    public static Expression constant(TypeDefinition type, Object value) {
        return new ConstantExpression(value);
    }

    /**
     * Declares a constant (no field).
     * Variable type is infered from the initializing expression.
     */
    public static Statement declaration(String variable, Expression initializer) {
        return new DeclarationStatement(parameter(variable, initializer.getType()), initializer);
    }

    public static Statement declaration(ParameterExpression variable, Expression initializer) {
        return new DeclarationStatement(variable, initializer);
    }

    /**
     * Declares a constant (no field), which is not initialized.
     */
    public static Statement declaration(String variable, TypeDefinition type) {
        return new DeclarationStatement(parameter(variable, type), null);
    }

    /**
     * Reference to a field in the surrounding class.
     */
    public static Expression field(String name) {
        return new FieldReference(UnknownType.INSTANCE, name);
    }

    /**
     * Reference to a field on the given target
     */
    public static Expression field(Node target, String name) {
        return new FieldReference(UnknownType.INSTANCE, name, target);
    }

    /**
     * Adds a line of comment.
     */
    public static Node comment(String comment) {
        return new LineComment(comment);
    }

    /**
     * Simple call to a method which throws no exception and that stuff.
     */
    public static Expression call(Node target, String methodName, Node... arguments) {
        return new CallExpression(
            new Method(UnknownType.INSTANCE, methodName, UnknownType.INSTANCE,
                Collections.<TypeDefinition>emptyList(), Collections.<ExceptionType>emptyList()),
            target,
            arguments
        );
    }

    public static Expression call(Method method, Node target, Node... arguments) {
        return new CallExpression(method, target, arguments);
    }

    /**
     * Static call.
     */
    public static Expression call(Method method, Node... arguments) {
        return new CallExpression(method, null, arguments);
    }

    /**
     * Reference to a Method, similar than field-reference
     * @return
     */
    public static Method method(TypeDefinition definingClass, String name, TypeDefinition returnType, List<TypeDefinition> parameterTypes, List<ExceptionType> exceptions) {
        return new Method(definingClass, name, returnType, parameterTypes, exceptions);
    }

    /**
     * References a variable.
     */
    public static ParameterExpression parameter(String name, TypeDefinition type) {
        return new ParameterExpression(type, name);
    }

    /**
     * New Instance of Class / Type.
     */
    public static Expression new_(TypeDefinition type, Node... arguments) {
        return new NewExpression(type, arguments);
    }

    /**
     * New Instance of Class / Type.
     */
    public static Expression new_(TypeDefinition type, List<Node> arguments) {
        return new NewExpression(type, arguments);
    }

    /**
     * Return Statements.
     */
    public static ReturnStatement return_(Expression value) {
        return new ReturnStatement(value);
    }

    /**
     * Declares a Class as type.
     */
    public static TypeDefinition typeOf(String className) {
        return new TypeDefinition(className);
    }
}
